package sim1011.behaviours;

import sim1011.components.Component;
import sim1011.poltergeist.Poltergeist;
import sim1011.starter.EctoMobileStarter;
import sim1011.starter.Logger;
import sim1011.status.NetworkMatrix;
import sim1011.status.Status;
import jade.core.AID;
import jade.core.behaviours.CyclicBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

public class PoltergeistMainBehaviour extends CyclicBehaviour implements
		java.io.Serializable {
	private static final long serialVersionUID = -2653581944344937874L;
	private AID[] components;

	@Override
	public void action() {
		MessageTemplate tp = MessageTemplate
				.MatchPerformative(ACLMessage.PROPAGATE);
		ACLMessage msg = myAgent.receive(tp);
		if (msg != null) {
			((Poltergeist) myAgent).getStatus()
					.increaseIncomingMessagesToPoltergeist();
			if (msg.getContent().equals("move out")) {
				Logger.log("[POLTERGEIST] Ricevuto messaggio dal mobile node: muovere tutti i componenti!");
				components = findAllComponentsOnMobileNode();
				for (AID component : components) {
					if (component != null) {
						ACLMessage moveOut = new ACLMessage(
								ACLMessage.PROPAGATE);
						moveOut.addReceiver(component);
						String cID = ((Poltergeist) myAgent).getStatus()
								.whereIsComponent(component.getLocalName());
						Logger.log("[POLTERGEIST] interrogato l' arbitro per il componente "
								+ component.getLocalName()
								+ " sul nodo "
								+ cID
								+ " a causa di low battery");
						String whereToMove = ((Poltergeist) myAgent)
								.requestMigrationDecisionOnLowBattery(cID,
										component.getLocalName());
						Logger.log("[POLTERGEIST] muovo "
								+ component.getLocalName()
								+ " in seguito a scaricamento batteria su: "
								+ whereToMove);
						moveOut.setContent(whereToMove + "#" + ((Poltergeist)myAgent).getStatus().getLinkQoS(cID, whereToMove));
						((Poltergeist)myAgent).getStatus().increaseMigrationCounter(component.getLocalName(), whereToMove);
						((Poltergeist) myAgent).getStatus().addCost(
								migrationCost(component.getLocalName(), cID,
										whereToMove));
						((Poltergeist) myAgent).getStatus().moveComponent(
								component.getLocalName(), whereToMove);
						((Poltergeist) myAgent).getStatus()
								.increaseOutcomingMessagesToPoltergeist();
						myAgent.send(moveOut);
					}
				}
			}
		} else {
			tp = MessageTemplate.MatchPerformative(ACLMessage.INFORM);
			msg = myAgent.receive(tp);
			if (msg != null) {
				((Poltergeist) myAgent).getStatus()
						.increaseIncomingMessagesToPoltergeist();
				String who = msg.getContent();
				Logger.log("[POLTERGEIST] ricevuta richiesta di esecuzione di: Comp."
						+ who
						+ " da parte di: "
						+ msg.getSender().getLocalName());
				ACLMessage forw = new ACLMessage(ACLMessage.INFORM);
				components = findAllComponents();
				AID rec = null;
				for (AID component : components) {
					if (component.getLocalName().equals("Comp." + who)) {
						rec = component;
						break;
					}
				}
				String currentLocation = ((Poltergeist) myAgent).getStatus()
						.whereIsComponent(rec.getLocalName());
				if (currentLocation.equals(EctoMobileStarter.MOBILE_NODE_NAME)
						&& !NetworkMatrix.mobileReachable) {
					// choose another "equivalent" component
					int k = EctoMobileStarter.r.nextInt(components.length);
					while (components[k].getLocalName() == rec.getLocalName()  //ci va l'equals
							&& (((Poltergeist) myAgent).getStatus()
									.whereIsComponent(
											components[k].getLocalName())
									.equals(EctoMobileStarter.MOBILE_NODE_NAME)))
						k = EctoMobileStarter.r.nextInt(components.length);
					rec = components[k];
					currentLocation = ((Poltergeist) myAgent).getStatus()
							.whereIsComponent(rec.getLocalName());
				}
				forw.addReceiver(rec);
				Logger.log("[POLTERGEIST] interrogato l' arbitro per il componente "
						+ rec.getLocalName() + " sul nodo " + currentLocation);
				String whereToExecute = ((Poltergeist) myAgent)
						.requestMigrationDecision(currentLocation,
								rec.getLocalName());
				if (whereToExecute.equals(""))
					whereToExecute = currentLocation;
				//aggiungo la QoS nel messaggio verso il componente
				String whereToExecuteQoS = whereToExecute + "#" + ((Poltergeist)myAgent).getStatus()
								.getNetworkMatrix()
								.getLinkQoS(((Poltergeist)myAgent).getStatus().whereIsComponent(rec.getLocalName()), whereToExecute);
				forw.setContent(whereToExecuteQoS);
				if (whereToExecute.equals(((Poltergeist) myAgent).getStatus()
						.whereIsComponent(rec.getLocalName())))
					Logger.log("[POLTERGEIST] faccio eseguire: "
							+ rec.getLocalName() + " sul nodo dove si trova");
				else {
					((Poltergeist) myAgent).getStatus()
							.increaseMigrationCounter(rec.getLocalName(), whereToExecute);
					((Poltergeist) myAgent).getStatus().addCost(
							migrationCost(rec.getLocalName(), currentLocation,
									whereToExecute));
					Logger.log("[POLTERGEIST] faccio eseguire: "
							+ rec.getLocalName() + " sul nodo "
							+ whereToExecute);
				}
				((Poltergeist) myAgent).getStatus().moveComponent(
						rec.getLocalName(), whereToExecute);
				((Poltergeist) myAgent).getStatus()
						.increaseOutcomingMessagesToPoltergeist();
				myAgent.send(forw);
			}
		}
		block();
	}

	private AID[] findAllComponents() {
		// Find all Components
		DFAgentDescription template = new DFAgentDescription();
		ServiceDescription sd = new ServiceDescription();
		sd.setType("ecto-components");
		template.addServices(sd);
		try {
			DFAgentDescription[] result = DFService.search(myAgent, template);
			AID[] cmp = new AID[result.length];
			for (int i = 0; i < result.length; ++i) {
				cmp[i] = result[i].getName();
			}
			return cmp;
		} catch (FIPAException e) {
			e.printStackTrace();
		}
		return null;
	}

	private AID[] findAllComponentsOnMobileNode() {
		AID[] allCMP = findAllComponents();
		AID[] ret = new AID[allCMP.length];
		int k = 0;
		for (int i = 0; i < allCMP.length; ++i) {
			String cID = ((Poltergeist) myAgent).getStatus().whereIsComponent(
					allCMP[i].getLocalName());
			if (cID == EctoMobileStarter.MOBILE_NODE_NAME) {
				ret[k] = allCMP[i];
				k++;
			}
		}
		return ret;
	}

	private Float migrationCost(String comp, String mitt, String dest) {
		float migration_cost = 0.0f;
		Status status = ((Poltergeist) myAgent).getStatus();
		// calcola la packet size come un overhead di migrazione +
		// un ipotetico payload funziona di memoria e cpu richiesti dal
		// componente
		float packet_size = EctoMobileStarter.MIGRATION_OVERHEAD
				+ Component.size(status.getComponentsRequiredCPU().get(comp).intValue(),
						status.getComponentsRequiredMemory().get(comp).intValue());
		// calcola il migration cost come funzione della qualità del
		// collegamento tra nodo
		// mittente (mitt) e nodo destinatario (dest) e della packet size
		if(!mitt.equals(dest)) {
			//System.err.println("[POLTERGEIST] mitt: " +mitt+" dest " +dest);
			float quality = status.getLinkQoS(mitt, dest).floatValue();
			quality = quality - 5.0f;
			if (quality != 0.0f)
				migration_cost = packet_size * (1 / quality);
			else
				migration_cost = packet_size * (1/0.0001f);
		}
		// più è grande il pacchetto più il costo di migrazione aumenta
		// più è piccola la qualità più è costoso (ci mette idealmente più
		// tempo) fare
		// la migrazione
		return migration_cost;
	}
}